#include "sdkconfig.h"
#ifdef CONFIG_DRIVER_USE_OTA
#include "http_server.h"
#include <string.h>
#include "ota.h"
#include "esp_image_format.h"
#include "esp_log.h"
static const char *TAG = "HTTP/OTA";

static esp_err_t ota_failed_handle(httpd_req_t *req)
{
    if (ota_default_handle->status != 0 && ota_default_handle->status != 1)
        httpd_resp_sendstr_chunk(req, ota_default_handle->err_str);
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}

static esp_err_t ota_app_stream_upgrade_handle(httpd_req_t *req)
{
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST,OPTIONS");
    // httpd_resp_set_hdr(req, "Transfer-Encoding", "chunked");
    // httpd_resp_set_type(req, "application/octet-stream");
    http_server_t server = httpd_get_global_user_ctx(req->handle);
    /* 接收缓冲区 */
    char *buf = (char *)server->data;
    /* 升级文件总长度 */
    uint32_t remaining = req->content_len;
    /* 当前接收的长度 */
    int received;
    ESP_LOGI(TAG, "Receiving ota app stream, length : %ld %d", remaining, req->method);

    const char *header_str = "Content-Range";
    size_t range = httpd_req_get_hdr_value_len(req, header_str); // 获取Content-Range的长度
    if (range <= 0)
    {
        header_str = "content-range";
        range = httpd_req_get_hdr_value_len(req, header_str); // 获取Content-Range的长度
    }
    if (range > 0)
    {
        if (httpd_req_get_hdr_value_str(req, header_str, buf, sizeof(server->data)) == ESP_OK)
        {
            sscanf(buf, "bytes %lu-", &ota_default_handle->offset);
            ESP_LOGI(TAG, "Content-Range: %s (%lu)", buf, ota_default_handle->offset);
        }
        else
        {
            ESP_LOGE(TAG, "Failed to get %s", header_str);
            return ota_failed_handle(req);
        }
    }

    ota_default_handle->length = remaining;
    ota_default_handle->write_size = 0;
    ota_default_handle->status = OTA_STATUS_NULL;
    ota_default_handle->progress = 0;
    ota_default_handle->type = OTA_TYPE_FILE | OTA_TYPE_APP;
    uint8_t last_progress = 0;
    while (remaining > 0)
    {
        /* Receive the file part by part into a buffer */
        if ((received = httpd_req_recv(req, buf, MIN(remaining, 4096))) <= 0)
        {
            if (received == HTTPD_SOCK_ERR_TIMEOUT)
            {
                /* Retry if timeout occurred */
                ESP_LOGW(TAG, "Stream receive timeout");
                continue;
            }
            /* In case of unrecoverable error,
             * close and delete the unfinished file*/
            ESP_LOGE(TAG, "Stream reception failed!");
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_chunk(req, "Failed to receive stream", HTTPD_RESP_USE_STRLEN);
            return ESP_OK;
        }
        if (ota_app_bin_stream_process_cb(NULL, (uint8_t *)buf, received))
        {
            ESP_LOGE(TAG, "Faile to write data to app partition from stream.");
            return ota_failed_handle(req);
        }
        if (ota_default_handle->status == OTA_STATUS_FAILED_CALL_ABORT)
            break;
        if (last_progress != ota_default_handle->progress)
        {
            last_progress = ota_default_handle->progress;
            httpd_resp_send_chunk(req, (char *)&last_progress, 1);
        }
        remaining -= received;
    }
    if (ota_app_bin_stream_process_cb(NULL, 0, 0))
    {
        ESP_LOGE(TAG, "Faile to end stream process.");
        return ota_failed_handle(req);
    }
    /* Redirect onto root to see the updated file list */
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}

static esp_err_t ota_www_stream_upgrade_handle(httpd_req_t *req)
{
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST,OPTIONS");
    // httpd_resp_set_hdr(req, "Content-Length", "100");
    // httpd_resp_set_hdr(req, "Transfer-Encoding", "chunked");
    httpd_resp_set_type(req, "application/octet-stream");
    // if (http_server_auth_check(req))
    // {
    //     httpd_resp_sendstr_chunk(req, NULL);
    //     return ESP_OK;
    // }
    http_server_t server = httpd_get_global_user_ctx(req->handle);
    /* 接收缓冲区 */
    char *buf = (char *)server->data;
    /* 升级文件总长度 */
    uint32_t remaining = req->content_len;
    /* 当前接收的长度 */
    int received;
    ESP_LOGI(TAG, "Receiving ota storage stream, length : %ld", remaining);

    const char *header_str = "Content-Range";
    size_t range = httpd_req_get_hdr_value_len(req, header_str); // 获取Content-Range的长度
    if (range <= 0)
    {
        header_str = "content-range";
        range = httpd_req_get_hdr_value_len(req, header_str); // 获取Content-Range的长度
    }
    if (range > 0)
    {
        if (httpd_req_get_hdr_value_str(req, header_str, buf, sizeof(server->data)) == ESP_OK)
        {
            sscanf(buf, "bytes %lu-", &ota_default_handle->offset);
            ESP_LOGI(TAG, "Content-Range: %s (%lu)", buf, ota_default_handle->offset);
        }
        else
        {
            ESP_LOGE(TAG, "Failed to get %s", header_str);
            return ota_failed_handle(req);
        }
    }

    ota_default_handle->length = remaining;
    ota_default_handle->write_size = 0;
    ota_default_handle->status = OTA_STATUS_NULL;
    ota_default_handle->progress = 0;
    ota_default_handle->type = OTA_TYPE_FILE | OTA_TYPE_WWW;
    uint16_t last_progress = 0;
    while (remaining > 0)
    {
        /* Receive the file part by part into a buffer */
        if ((received = httpd_req_recv(req, buf, MIN(remaining, 4096))) <= 0)
        {
            if (received == HTTPD_SOCK_ERR_TIMEOUT)
            {
                /* Retry if timeout occurred */
                ESP_LOGW(TAG, "Stream receive timeout");
                continue;
            }
            /* In case of unrecoverable error,
             * close and delete the unfinished file*/
            ESP_LOGE(TAG, "Stream reception failed!");
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_chunk(req, "Failed to receive stream", HTTPD_RESP_USE_STRLEN);
            httpd_resp_send_chunk(req, NULL, 0);
            ota_www_bin_stream_process_cb(NULL, 0, 0);
            return ESP_FAIL;
        }
        if (ota_www_bin_stream_process_cb(NULL, (uint8_t *)buf, received))
        {
            ESP_LOGE(TAG, "Faile to write data to www partition from stream.");
            return ota_failed_handle(req);
        }
        if (ota_default_handle->status == OTA_STATUS_FAILED_CALL_ABORT)
            break;
        if (last_progress != ota_default_handle->progress)
        {
            last_progress = ota_default_handle->progress;
            httpd_resp_send_chunk(req, (char *)&last_progress, 1);
        }
        remaining -= received;
    }
    if (ota_www_bin_stream_process_cb(NULL, 0, 0))
    {
        ESP_LOGE(TAG, "Faile to end stream process.");
        return ota_failed_handle(req);
    }
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}

static esp_err_t ota_ctrl_handle(httpd_req_t *req)
{
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST,OPTIONS");
    // if (http_server_auth_check(req))
    // {
    //     httpd_resp_sendstr_chunk(req, NULL);
    //     return ESP_OK;
    // }
    http_server_t server = httpd_get_global_user_ctx(req->handle);
    cJSON *json = http_server_data_parse_to_json(req, 1);
    if (json)
    {
        if (json_object_has_item(NULL, json, "checkVersion"))
        {
            int type = cJSON_GetInt(json, "checkVersion");
            if (type < 3)
            {
                ota_default_handle->checkVersion = type;
                req_send_ok();
                httpd_resp_sendstr_chunk(req, NULL);
                cJSON_Delete(json);
                return ESP_OK;
            }
        }
        else if (json_object_has_item(NULL, json, "back"))
        {
            const esp_partition_t *prt = esp_ota_get_next_update_partition(NULL);
            esp_image_metadata_t data;
            const esp_partition_pos_t part_pos = {
                .offset = prt->address,
                .size = prt->size,
            };

            if (esp_image_verify(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK)
                req_send_err("Validate failed");
            else
            {
                if (esp_ota_set_boot_partition(prt) != ESP_OK)
                    req_send_err("Set boot partition failed");
                else
                {
                    ota_default_handle->tick = esp_log_timestamp() + 5000;
                    req_send_ok();
                }
            }
            httpd_resp_sendstr_chunk(req, NULL);
            cJSON_Delete(json);
            return ESP_OK;
        }
        else if (json_object_has_item(NULL, json, "abort"))
        {
            ota_default_handle->status = OTA_STATUS_FAILED_CALL_ABORT;
            snprintf(ota_default_handle->err_str, sizeof(ota_default_handle->err_str), "Aborted by WEB API");
            req_send_ok();
            httpd_resp_sendstr_chunk(req, NULL);
            cJSON_Delete(json);
            return ESP_OK;
        }
        cJSON_Delete(json);
    }
    req_send_err("{\"code\":-1, \"data\":\"Unknown type\"}");
    httpd_resp_sendstr_chunk(req, NULL);
    return ESP_OK;
}

static esp_err_t ota_options_handle(httpd_req_t *req)
{
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST,OPTIONS");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Range, Content-Type");
    httpd_resp_sendstr_chunk(req, NULL);
    return ESP_OK;
}

void ota_server_init(httpd_handle_t server, char *base_path)
{
    if (!server)
        return;
    httpd_uri_t ota_ctrl = {
        .uri = "/upgrade/ctrl/app", /* file stream ota app api */
        .method = HTTP_POST,
        .handler = ota_ctrl_handle};
    httpd_register_uri_handler(server, &ota_ctrl);
    httpd_uri_t ota_app_stream_upgrade = {
        .uri = "/upgrade/stream/app", /* file stream ota app api */
        .method = HTTP_POST,
        .handler = ota_app_stream_upgrade_handle};
    httpd_register_uri_handler(server, &ota_app_stream_upgrade);
    httpd_uri_t ota_www_stream_upgrade = {
        .uri = "/upgrade/stream/www", /* file stream ota www api */
        .method = HTTP_POST,
        .handler = ota_www_stream_upgrade_handle};
    httpd_register_uri_handler(server, &ota_www_stream_upgrade);
    httpd_uri_t ota_stream_options = {
        .uri = "/upgrade/stream/*",
        .method = HTTP_OPTIONS,
        .handler = ota_options_handle};
    httpd_register_uri_handler(server, &ota_stream_options);
}
#endif